﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

namespace VersionComparer.Base
{
    /// <summary>
    /// This class manages thread execution
    /// </summary>
    public class ThreadManager
    {
        #region statics
        static ThreadManager current = new ThreadManager ();
        public static ThreadManager Current
        {
            get
            {
                return current;
            }
        }

        private static WorkerInfoBase GetWorkerFromList(List<WorkerInfoBase> workers, string name)
        {
            return workers.FirstOrDefault(x => x.Name == name);
        }
        private static WorkerInfoBase GetWorkerFromList(List<WorkerInfoBase> workers, WorkerInfoBase worker)
        {
            if (worker == null || workers == null)
                return null;
            return GetWorkerFromList(workers, worker.Name);
        }
        private static void ReplaceInList(List<WorkerInfoBase> workers, WorkerInfoBase worker)
        {
            lock (workers)
            {
                var existingWorker = GetWorkerFromList(workers, worker);
                if (existingWorker == null)
                    workers.Add(worker);
                else
                {
                    workers.Remove(existingWorker);
                    workers.Add(worker);
                }

            }
        }

        #endregion statics

        #region constructors
        public ThreadManager()
        {
        }
        #endregion constructors

        #region fields
        object syncObject = new object();
        List<WorkerInfoBase> workers = new List<WorkerInfoBase>();
        List<WorkerInfoBase> queuedWorkers = new List<WorkerInfoBase>();
        #endregion fields

        public bool WaitForThread(Action<ManagedThread> action, int? millisecondsToWait = null)
        {
            return WaitForThread(action.Method.ToString(), action, millisecondsToWait);
        }
        public bool WaitForThread(string name, Action<ManagedThread> action, int? millisecondsToWait = null)
        {
            return WaitForThread(name,  millisecondsToWait);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <param name="millisecondsToWait"></param>
        /// <returns>true if thread has terminated, false otherwise</returns>
        public bool WaitForThread(string name, int? millisecondsToWait = null)
        {
            WorkerInfoBase runningWorker;
            lock (syncObject)
            {
                runningWorker = GetWorkerFromList(workers, name);
            }
            if (runningWorker != null)
                return runningWorker.Wait(millisecondsToWait);
            else
                return true;
        }
        public WorkerInfoBase StartNew(Action action)
        {
            return StartNew(action.Method.ToString(), (x) => action());
        }
        public WorkerInfoBase StartNew(Action<ManagedThread> action)
        {
            return StartNew(action.Method.ToString(), action);
        }
        public WorkerInfoBase StartNew(string name, Action<ManagedThread> action)
        {
            return StartNew<object>(name, (thread,x) => action(thread), null);
        }
        public WorkerInfo<T> StartNew<T>(Action<T> action, T argument)
        {
            return StartNew<T>(action.Method.ToString(), (x, y) => action(y), argument);
        }
        public WorkerInfo<T> StartNew<T>(Action<ManagedThread, T> action, T argument)
        {
            return StartNew<T>(action.Method.ToString(), action, argument);
        }
        public WorkerInfo<T> StartNew<T>(string name, Action<ManagedThread, T> action, T argument)
        {
            Stop ( name );

            BackgroundWorker worker = new BackgroundWorker ();
            WorkerInfo <T> workerInfo = new WorkerInfo<T> (name , action, argument, worker);
            bool startRunning = false;
            lock (syncObject)
            {
                if (GetWorkerFromList(workers, workerInfo) != null)
                    ReplaceInList(queuedWorkers, workerInfo);
                else
                {
                    workers.Add(workerInfo);
                    startRunning = true;
                }
            }
            if (startRunning)
                Start(workerInfo);
            return workerInfo;
        }

        private void Start(WorkerInfoBase worker)
        {
            worker.Worker.WorkerSupportsCancellation = true;
            worker.Worker.DoWork += worker_DoWork;
            worker.Worker.RunWorkerCompleted += worker_RunWorkerCompleted;
            worker.Worker.RunWorkerAsync(worker);
        }
        public bool IsRunning<T>(Action<ManagedThread, T> action)
        {
            return IsRunning(action.Method.ToString());
        }
        public bool IsRunning(Action<ManagedThread> action)
        {
            return IsRunning(action.Method.ToString());
        }
        public bool IsRunning(string name)
        {
            lock (syncObject)
            {
                return workers.FirstOrDefault(x => x.Name == name) != null;
            }
        }
        public void Stop(WorkerInfoBase worker)
        {
            if (worker == null)
                return;
            worker.Worker.CancelAsync();
        }
        public void Stop<T>(Action<ManagedThread,T> action)
        {
            Stop ( action.ToString ());
        }
        public void Stop(Action<ManagedThread> action)
        {
            Stop(action.ToString());
        }
        public void Stop(string name)
        {
            lock (syncObject)
            {
                Stop(workers.FirstOrDefault(x => x.Name == name));
            }
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {

            try
            {
                (e.Argument as WorkerInfoBase).Execute();
            }
            catch
            {
            }
        }
        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker ;
            lock (syncObject)
            {
                string name;
                var runningWorker = workers.FirstOrDefault(x => x.Worker == worker);
                if (runningWorker != null)
                {
                    workers.Remove(runningWorker);
                    name = runningWorker.Name;
                    var newWorker = GetWorkerFromList(queuedWorkers, runningWorker);
                    if (newWorker != null)
                    {
                        queuedWorkers.Remove(newWorker);
                        Start(newWorker);
                    }
                    runningWorker.Terminated();
                }
            }
        }
    }
}
